package cn.coufran.springboot.starter.auth.impl.token;

import cn.coufran.commons.exception.ServiceException;
import cn.coufran.springboot.starter.auth.AuthUser;
import cn.coufran.springboot.starter.auth.AuthUserManager;
import cn.coufran.springboot.starter.auth.Certificate;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;

import java.util.Date;
import java.util.List;

/**
 * 基于Token的权限上下文
 * @author Coufran
 * @version 1.1.0
 * @since 1.0.0
 */
public class TokenAuthUserManager implements AuthUserManager {
    /** 默认的Token过期时间 */
    private static final int EXPIRE_TIME_DEFAULT = 2 * 60 * 60; // 2h
    /** 默认的RefreshToken过期时间 */
    private static final int REFRESH_EXPIRE_TIME_DEFAULT = 2 * 24 * 60 * 60; // 2d

    /** Payload字段：数据 */
    private static final String PAYLOAD_KEY_DATA = "d";
    /** Payload字段：转换器名称 */
    private static final String PAYLOAD_KEY_CONVERTER = "c";
    /** Payload字段：权限类型 */
    private static final String PAYLOAD_KEY_TYPE = "t";

    /** Token注册机 */
    private TokenRegister tokenRegister;
    /** Payload转换器 */
    private List<TokenPayloadConverter<?>> tokenPayloadConverters;
    /** Refresh Token存储 */
    private RefreshTokenRepository refreshTokenRepository;

    /**
     * 构造权限上下文
     * @param tokenRegister Token注册机
     * @param tokenPayloadConverters Payload转换器
     * @param refreshTokenRepository Refresh Token存储
     */
    public TokenAuthUserManager(TokenRegister tokenRegister, List<TokenPayloadConverter<?>> tokenPayloadConverters, RefreshTokenRepository refreshTokenRepository) {
        this.tokenRegister = tokenRegister;
        this.tokenPayloadConverters = tokenPayloadConverters;
        this.refreshTokenRepository = refreshTokenRepository;
    }

    /**
     * 重新加载实名用户，解析Token为数据，然后重新构造
     * @param certificate 证书
     * @param <D> 权限用户基础数据类型
     * @return 权限用户
     */
    @Override
    public <D> AuthUser<D> reloadAuthenticated(Certificate certificate) {
        // 判断证书类型
        if (!(certificate instanceof TokenCertificate)) {
            return null;
        }
        // 获取Token
        TokenCertificate tokenCertificate = (TokenCertificate) certificate;
        String token = tokenCertificate.getAccessToken();
        if (token == null) {
            return null;
        }
        // 校验并解析为负载
        String payload = tokenRegister.parse(token);
        if (payload == null) {
            return null;
        }
        // payload转data
        JSONObject payloadJson = JSON.parseObject(payload);
        D data = null;
        String convertorName = payloadJson.getString(PAYLOAD_KEY_CONVERTER);
        for (TokenPayloadConverter<?> tokenPayloadConverter : tokenPayloadConverters) {
            if (!convertorName.equals(tokenPayloadConverter.getName())) {
                continue;
            }
            String dataPayload = payloadJson.getString(PAYLOAD_KEY_DATA);
            data = tokenPayloadConverter.deserialize(dataPayload);
            break;
        }
        if (data == null) {
            return null;
        }
        // 构造
        TokenAuthUser.Auth auth = payloadJson.getObject(PAYLOAD_KEY_TYPE, TokenAuthUser.Auth.class);
        return new TokenAuthUser<>(this, data, auth, token);
    }

    /**
     * 创建匿名用户
     * @param <D> 权限用户基础数据类型
     * @return 匿名用户
     */
    @Override
    public <D> AuthUser<D> createAnonymous() {
        return new TokenAuthUser<>(this);
    }

    /**
     * 权限用户登录上下文，并返回证书
     * @param authUser 权限用户
     * @param data 基础数据
     * @param <D> 权限用户基础数据类型
     * @return 证书
     */
    @Override
    public <D> Certificate acceptLogin(AuthUser<D> authUser, D data) {
        // data转payload
        JSONObject payloadJson = new JSONObject();
        for (TokenPayloadConverter tokenPayloadConverter : tokenPayloadConverters) {
            if (!tokenPayloadConverter.support(data)) {
                continue;
            }
            String dataPayload = tokenPayloadConverter.serialize(data);
            payloadJson.put(PAYLOAD_KEY_DATA, dataPayload);
            payloadJson.put(PAYLOAD_KEY_CONVERTER, tokenPayloadConverter.getName());
            break;
        }
        if (payloadJson.isEmpty()) {
            throw new ServiceException("not supported data: " + data);
        }
        return generateCertificate(payloadJson);
    }

    /**
     * 刷新AccessToken
     * @param authUser 权限用户
     * @param <D> 权限用户基础数据类型
     * @return 新的权限证书
     */
    @Override
    public <D> Certificate acceptRefresh(AuthUser<D> authUser) {
        TokenAuthUser<D> tokenAuthUser = (TokenAuthUser<D>) authUser;
        if (tokenAuthUser.getAuth() != TokenAuthUser.Auth.REFRESH) {
            throw new ServiceException("无权限");
        }
        String token = tokenAuthUser.getToken();
        boolean exists = refreshTokenRepository.delete(token);
        if (!exists) {
            throw new ServiceException("无权限");
        }
        return acceptLogin(authUser, authUser.getData());
    }

    private TokenCertificate generateCertificate(JSONObject payloadJson) {
        // 封装payload为token
        payloadJson.put(PAYLOAD_KEY_TYPE, TokenAuthUser.Auth.ACCESS);
        String accessToken = tokenRegister.generate(payloadJson.toJSONString(), EXPIRE_TIME_DEFAULT);
        payloadJson.put(PAYLOAD_KEY_TYPE, TokenAuthUser.Auth.REFRESH);
        String refreshToken = tokenRegister.generate(payloadJson.toJSONString(), REFRESH_EXPIRE_TIME_DEFAULT);
        Date expireTime = new Date(System.currentTimeMillis() + REFRESH_EXPIRE_TIME_DEFAULT * 1000);
        refreshTokenRepository.save(refreshToken, expireTime);
        // 封装token为certificate
        return new TokenCertificate(accessToken, EXPIRE_TIME_DEFAULT, refreshToken, REFRESH_EXPIRE_TIME_DEFAULT);
    }

    /**
     * 不支持登出，直接返回
     * @param authUser 权限用户
     * @param <U> 权限用户基础数据类型
     */
    @Override
    public <U> void acceptLogout(AuthUser<U> authUser) {

    }
}
